/**
 * 类型检查和字符串验证工具
 *
 * 提供各种类型检查和常用字符串验证功能的工具集合
 *
 * @packageDocumentation
 */

/**
 * 检查变量是否为字符串类型
 * @param o - 待检查的变量
 * @returns 如果是字符串返回true，否则返回false
 */
export const isString = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'String';
};

/**
 * 检查变量是否为数字类型
 * @param o - 待检查的变量
 * @returns 如果是数字返回true，否则返回false
 */
export const isNumber = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Number';
};

/**
 * 检查变量是否为布尔类型
 * @param o - 待检查的变量
 * @returns 如果是布尔值返回true，否则返回false
 */
export const isBoolean = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Boolean';
};

/**
 * 检查变量是否为函数类型
 * @param o - 待检查的变量
 * @returns 如果是函数返回true，否则返回false
 */
export const isFunction = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Function';
};

/**
 * 检查变量是否为null
 * @param o - 待检查的变量
 * @returns 如果是null返回true，否则返回false
 */
export const isNull = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Null';
};

/**
 * 检查变量是否为undefined
 * @param o - 待检查的变量
 * @returns 如果是undefined返回true，否则返回false
 */
export const isUndefined = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Undefined';
};

/**
 * 检查变量是否为对象类型
 * @param o - 待检查的变量
 * @returns 如果是对象返回true，否则返回false
 */
export const isObj = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Object';
};

/**
 * 检查变量是否为数组类型
 * @param o - 待检查的变量
 * @returns 如果是数组返回true，否则返回false
 */
export const isArray = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Array';
};

/**
 * 检查变量是否为日期类型
 * @param o - 待检查的变量
 * @returns 如果是日期对象返回true，否则返回false
 */
export const isDate = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Date';
};

/**
 * 检查变量是否为正则表达式类型
 * @param o - 待检查的变量
 * @returns 如果是正则表达式返回true，否则返回false
 */
export const isRegExp = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'RegExp';
};

/**
 * 检查变量是否为错误对象类型
 * @param o - 待检查的变量
 * @returns 如果是错误对象返回true，否则返回false
 */
export const isError = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Error';
};

/**
 * 检查变量是否为Symbol类型
 * @param o - 待检查的变量
 * @returns 如果是Symbol返回true，否则返回false
 */
export const isSymbol = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Symbol';
};

/**
 * 检查变量是否为Promise类型
 * @param o - 待检查的变量
 * @returns 如果是Promise返回true，否则返回false
 */
export const isPromise = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Promise';
};

/**
 * 检查变量是否为Set类型
 * @param o - 待检查的变量
 * @returns 如果是Set返回true，否则返回false
 */
export const isSet = (o: unknown): boolean => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Set';
};

/**
 * 检查变量是否为 NaN
 * @param o - 待检查的变量
 * @returns 如果是NaN返回true，否则返回false
 */
// biome-ignore lint/suspicious/noShadowRestrictedNames: 单独方法，特定作用于，无需biome
export const isNaN = (o: unknown): boolean => {
  return isNumber(o) && `${o}` === 'NaN';
};

/**
 * 检查变量是否为假值
 * @param o - 待检查的变量
 * @returns 如果是假值返回true，否则返回false
 * @remarks
 * 以下值被认为是假值：
 * - falsy值 (false, 0, '', null, undefined, NaN)
 * - 字符串 'null', 'undefined', 'false', 'NaN'
 */
export const isFalse = (o: unknown): boolean => {
  if (!o || o === 'null' || o === 'undefined' || o === 'false' || o === 'NaN')
    return true;
  return false;
};

/**
 * 检查变量是否为真值
 * @param o - 待检查的变量
 * @returns 如果是真值返回true，否则返回false
 */
export const isTrue = (o: unknown): boolean => {
  return !isFalse(o);
};

/**
 * 检查是否为iOS设备
 * @returns 如果是iOS设备返回true，否则返回false
 */
export const isIos = (): boolean => {
  const u = navigator.userAgent;
  if (u.indexOf('Android') > -1 || u.indexOf('Linux') > -1) {
    return false;
  } else if (u.indexOf('iPhone') > -1) {
    return true;
  } else if (u.indexOf('iPad') > -1) {
    return false;
  } else if (u.indexOf('Windows Phone') > -1) {
    return false;
  } else {
    return false;
  }
};

/**
 * 检查是否为PC端
 * @returns 如果是PC端返回true，否则返回false
 */
export const isPC = (): boolean => {
  const userAgentInfo = navigator.userAgent;
  const Agents = [
    'Android',
    'iPhone',
    'SymbianOS',
    'Windows Phone',
    'iPad',
    'iPod',
  ];
  let flag = true;
  for (let v = 0; v < Agents.length; v++) {
    if (userAgentInfo.indexOf(Agents[v]) > 0) {
      flag = false;
      break;
    }
  }
  return flag;
};

/**
 * 获取浏览器类型
 * @returns 返回浏览器类型字符串
 * @remarks
 * 可能的返回值：
 * - IE7, IE8, IE9, IE10, IE11
 * - Edge, FF, Opera, Safari, Chrome
 */
export const browserType = (): string | undefined => {
  const userAgent = navigator.userAgent;
  const isOpera = userAgent.indexOf('Opera') > -1;
  const isIE =
    userAgent.indexOf('compatible') > -1 &&
    userAgent.indexOf('MSIE') > -1 &&
    !isOpera;
  const isIE11 =
    userAgent.indexOf('Trident') > -1 && userAgent.indexOf('rv:11.0') > -1;
  const isEdge = userAgent.indexOf('Edge') > -1 && !isIE;
  const isFF = userAgent.indexOf('Firefox') > -1;
  const isSafari =
    userAgent.indexOf('Safari') > -1 && userAgent.indexOf('Chrome') === -1;
  const isChrome =
    userAgent.indexOf('Chrome') > -1 && userAgent.indexOf('Safari') > -1;

  if (isIE) {
    const reIE = /MSIE (\d+\.\d+);/;
    reIE.test(userAgent);
    const fIEVersion = parseInt(RegExp.$1);
    if (fIEVersion === 7) return 'IE7';
    else if (fIEVersion === 8) return 'IE8';
    else if (fIEVersion === 9) return 'IE9';
    else if (fIEVersion === 10) return 'IE10';
    else return 'IE7以下';
  }
  if (isIE11) return 'IE11';
  if (isEdge) return 'Edge';
  if (isFF) return 'FF';
  if (isOpera) return 'Opera';
  if (isSafari) return 'Safari';
  if (isChrome) return 'Chrome';
  return undefined;
};

/**
 * 常用字符串验证
 * @param str - 待验证的字符串
 * @param type - 验证类型
 * @returns 验证结果
 * @remarks
 * 支持的验证类型：
 * - phone: 手机号码
 * - tel: 座机号码
 * - card: 身份证号码
 * - pwd: 密码（有字母和数字，6到20位即可，不能有空格，其他不限）
 * - postal: 邮政编码
 * - QQ: QQ号码
 * - email: 邮箱地址
 * - money: 金额（小数点后2位）
 * - URL: 网址
 * - IP: IP地址
 * - date: 日期时间
 * - number: 数字
 * - english: 英文
 * - chinese: 中文
 * - lower: 小写字母
 * - upper: 大写字母
 * - HTML: HTML标记
 */
export const checkStr = (str: string | number, type: string): boolean => {
  switch (type) {
    case 'phone':
      return /^1[3|4|5|6|7|8|9][0-9]{9}$/.test(str as string);
    case 'tel':
      return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str as string);
    case 'card':
      return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(str as string);
    case 'pwd':
      return /^(?=.*[a-zA-Z])(?=.*\d)(?!.*\s)[\S]{6,20}$/.test(str as string);
    case 'postal':
      return /[1-9]\d{5}(?!\d)/.test(str as string);
    case 'QQ':
      return /^[1-9][0-9]{4,9}$/.test(str as string);
    case 'email':
      return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str as string);
    case 'money':
      return /^\d*(?:\.\d{0,2})?$/.test(str as string);
    case 'URL':
      return /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-.,@?^=%&:/~+#]*[\w\-@?^=%&/~+#])?/.test(
        str as string,
      );
    case 'IP':
      return /((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))/.test(
        str as string,
      );
    case 'date':
      return (
        /^(\d{4})-(\d{2})-(\d{2}) (\d{2})(?::\d{2}|:(\d{2}):(\d{2}))$/.test(
          str as string,
        ) || /^(\d{4})-(\d{2})-(\d{2})$/.test(str as string)
      );
    case 'number':
      return /^[0-9]$/.test(str as string);
    case 'english':
      return /^[a-zA-Z]+$/.test(str as string);
    case 'chinese':
      return /^[\u4E00-\u9FA5]+$/.test(str as string);
    case 'lower':
      return /^[a-z]+$/.test(str as string);
    case 'upper':
      return /^[A-Z]+$/.test(str as string);
    case 'HTML':
      return /<("[^"]*"|'[^']*'|[^'">])*>/.test(str as string);
    default:
      return true;
  }
};

/**
 * 严格的身份证校验
 * @param sId - 身份证号码
 * @returns 校验结果
 * @remarks
 * 校验规则包括：
 * 1. 长度和基本格式校验
 * 2. 地区代码校验
 * 3. 出生日期校验
 * 4. 最后一位校验码校验
 */
export const isCardID = (sId: string): boolean => {
  if (!/(^\d{15}$)|(^\d{17}(\d|X|x)$)/.test(sId)) {
    alert('你输入的身份证长度或格式错误');
    return false;
  }
  //身份证城市
  const aCity: Record<number, string> = {
    11: '北京',
    12: '天津',
    13: '河北',
    14: '山西',
    15: '内蒙古',
    21: '辽宁',
    22: '吉林',
    23: '黑龙江',
    31: '上海',
    32: '江苏',
    33: '浙江',
    34: '安徽',
    35: '福建',
    36: '江西',
    37: '山东',
    41: '河南',
    42: '湖北',
    43: '湖南',
    44: '广东',
    45: '广西',
    46: '海南',
    50: '重庆',
    51: '四川',
    52: '贵州',
    53: '云南',
    54: '西藏',
    61: '陕西',
    62: '甘肃',
    63: '青海',
    64: '宁夏',
    65: '新疆',
    71: '台湾',
    81: '香港',
    82: '澳门',
    91: '国外',
  };
  if (!aCity[parseInt(sId.substr(0, 2))]) {
    alert('你的身份证地区非法');
    return false;
  }

  // 出生日期验证
  const sBirthday = (
      sId.substr(6, 4) +
      '-' +
      Number(sId.substr(10, 2)) +
      '-' +
      Number(sId.substr(12, 2))
    ).replace(/-/g, '/'),
    d = new Date(sBirthday);
  if (sBirthday !== `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`) {
    alert('身份证上的出生日期非法');
    return false;
  }

  // 身份证号码校验
  let sum = 0;
  const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
  const codes = '10X98765432';
  for (let i = 0; i < sId.length - 1; i++) {
    sum += Number(sId[i]) * weights[i];
  }
  const last = codes[sum % 11]; //计算出来的最后一位身份证号码
  if (sId[sId.length - 1] !== last) {
    alert('你输入的身份证号非法');
    return false;
  }
  return true;
};

/**
 * 密码校验难度
 */
export enum pwdValidateType {
  /**简单 包含字母数字，最少6位就行 */
  easy,
  /**一般 包含大小写字母数字和特殊字符，至少三种，最少8位 */
  medium,
  /**困难 包含大小写字母数字和特殊字符，至少四种，最少8位 */
  strong,
}
/**
 * 密码校验
 * @param pwd 密码
 * @param options 配置项
 * @param options.type 密码强度
 * @param options.range 密码长度范围
 * @returns
 */
export const validatePwd = (
  pwd: string,
  options: { type: pwdValidateType; range?: [number, number] } = {
    type: pwdValidateType.easy,
  },
) => {
  if (options.range && options.range[1] > 128) {
    throw new Error('密码长度范围最大128位');
  }
  let range: [number, number];
  switch (options.type) {
    case pwdValidateType.strong:
      range = options.range ?? [8, 128];
      return new RegExp(
        `^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^a-zA-Z\\d]).{${range[0]},${range[1]}}$`,
      ).test(pwd);
    case pwdValidateType.medium:
      range = options.range ?? [8, 128];

      return new RegExp(
        // eslint-disable-next-line no-useless-escape
        `^(?:(?=.*[a-z])(?=.*[A-Z])(?=.*\d)|(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z\d])|(?=.*[a-z])(?=.*\d)(?=.*[^a-zA-Z\d])|(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]))[a-zA-Z0-9\S]{${range[0]},${range[1]}}$`,
      ).test(pwd);
    default:
      range = options.range ?? [6, 128];
      return new RegExp(`^[a-zA-Z0-9]{${range[0]},${range[1]}}$`).test(pwd);
  }
};
